//
//  CoGeRenderManager.m
//  CoGeOpenSource
//
//  Created by Tamas Nagy on 14/11/13.
//  Copyright (c) 2013 home. All rights reserved.
//

#import "CoGeRenderManager.h"
#import <OpenGL/CGLMacro.h>

@implementation CoGeRenderManager

@synthesize glContext;
@synthesize layerSize;
@synthesize canvasSize;
@synthesize needsMSAA;

+ (CoGeRenderManager *)sharedManager
{
    static dispatch_once_t once;
    static CoGeRenderManager * sharedManager = nil;
    
    dispatch_once(&once, ^{
        sharedManager = [[self alloc] init];
    });
    return sharedManager;
}

-(void)initWithContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pformat {
    
    NSOpenGLContext *c = [[NSOpenGLContext alloc] initWithFormat:pformat shareContext:context];
    
    [self setGlContext:c];
    
    [c release];
    
    cgl_ctx = [[self glContext] CGLContextObj];
    
    colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    
 
}



-(void)lock {
    
    CGLLockContext(cgl_ctx);
}
-(void)unlock {
    
    CGLUnlockContext(cgl_ctx);

}

-(void)renderingSizeChanged {
    
    rebuildlayer = YES;
    
    needsMSAAcached = [self needsMSAA];
    
    NSLog(@"new rendering sizes: %@ - %@", NSStringFromSize([self layerSize]), NSStringFromSize([self canvasSize]));
    
    if (!NSEqualSizes([self layerSize], [self canvasSize])) {
        
        rebuildcanvas = YES;
        
        needscanvas = YES;
    
    } else {
        
        rebuildcanvas = NO;
        needscanvas = NO;


        [self lock];
        
        
		if(canvasfbo)
		{
			glDeleteFramebuffersEXT(1, &canvasfbo);
			canvasfbo = 0;
		}
        
		if(canvasdepthbuffer)
		{
			glDeleteRenderbuffersEXT(1, &canvasdepthbuffer);
			canvasdepthbuffer = 0;
		}

        
        [self unlock];
    }
}

-(void)renderWithRenderer:(id<CoGeTextureSource>)renderer atTime:(NSTimeInterval)time arguments:(NSDictionary *)args  isCoreImageFX:(BOOL)isCI {
    
	//be safe and store the current size
    currentSize = [renderer renderSize];

	//be safe with this too
    useMSAA = needsMSAAcached;
    
	//NSLog(@"rendering height: %f", currentSize.height);
    
    [self lock];
    
    if(rebuildcanvas)
	{
        
        NSSize canvassize = [self canvasSize];
        
		if(canvasfbo)
		{
			glDeleteFramebuffersEXT(1, &canvasfbo);
			canvasfbo = 0;
		}
        
		if(canvasdepthbuffer)
		{
			glDeleteRenderbuffersEXT(1, &canvasdepthbuffer);
			canvasdepthbuffer = 0;
		}
        
        
		GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
		if(status != GL_FRAMEBUFFER_COMPLETE)
		{
			NSLog(@"CoGeRenderManager OpenGL error %04X", status);
			glDeleteFramebuffersEXT(1, &canvasfbo);
			glDeleteRenderbuffersEXT(1, &canvasdepthbuffer);
			canvasfbo = 0;
			canvasdepthbuffer = 0;
			CGLUnlockContext(cgl_ctx);
			return;
		}
        
		
		// depth buffer
		glGenRenderbuffersEXT(1, &canvasdepthbuffer);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, canvasdepthbuffer);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, canvassize.width, canvassize.height);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
        
		
		// FBO and connect attachments
		glGenFramebuffersEXT(1, &canvasfbo);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, canvasfbo);
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER_EXT, canvasdepthbuffer);
		// Draw black so we have output if the renderer isn't loaded
        //	glClearColor(0.0, 0.0, 0.0, 0.0);
        //	glClear(GL_COLOR_BUFFER_BIT);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        //	glDisable(GL_TEXTURE_RECTANGLE_EXT);
		
        rebuildcanvas = NO;
	}

	
	if(rebuildlayer)
	{
        
        NSSize layersize = [self layerSize];
        
        
		if(fbo)
		{
			glDeleteFramebuffersEXT(1, &fbo);
			fbo = 0;
		}

        if(msaafbo)
		{
			glDeleteFramebuffersEXT(1, &msaafbo);
			msaafbo = 0;
		}

        if(renderbuffer)
		{
			glDeleteRenderbuffersEXT(1, &renderbuffer);
			renderbuffer = 0;
		}

		if(depthbuffer)
		{
			glDeleteRenderbuffersEXT(1, &depthbuffer);
			depthbuffer = 0;
		}
        
        

        if (useMSAA) {
            
            // depth buffer
            glGenRenderbuffersEXT(1, &depthbuffer);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
            glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, 4, GL_DEPTH_COMPONENT, layersize.width, layersize.height);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

            //render buffer
            glGenRenderbuffersEXT(1, &renderbuffer);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffer);
            glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, 4, GL_RGBA8, layersize.width, layersize.height);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
            
            
            // MSAAFBO and connect attachments
            glGenFramebuffersEXT(1, &msaafbo);
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, msaafbo);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, renderbuffer);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

            //our normal FBO
            glGenFramebuffersEXT(1, &fbo);

        } else {
            
            // depth buffer
            glGenRenderbuffersEXT(1, &depthbuffer);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, layersize.width, layersize.height);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

            // FBO and connect attachments
            glGenFramebuffersEXT(1, &fbo);
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
            
        }
        
		
		// Draw black so we have output if the renderer isn't loaded
        //	glClearColor(0.0, 0.0, 0.0, 0.0);
        //	glClear(GL_COLOR_BUFFER_BIT);
		//glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        //	glDisable(GL_TEXTURE_RECTANGLE_EXT);

        GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
		if(status != GL_FRAMEBUFFER_COMPLETE)
		{
			NSLog(@"CoGeRenderManager OpenGL error %04X", status);
			glDeleteFramebuffersEXT(1, &fbo);
			glDeleteRenderbuffersEXT(1, &depthbuffer);
			fbo = 0;
			depthbuffer = 0;
			CGLUnlockContext(cgl_ctx);
			return;
		}

        rebuildlayer = NO;

	}
    
    if (needscanvas && NSEqualSizes(currentSize, [self canvasSize])) {
        
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, canvasfbo);
      //  NSLog(@"will drawing on canvas!");
    } else {
        
        if (useMSAA && [renderer canUseMSAA])
        {
            
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, msaafbo);
            
        } else {
            
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

        }

    }
    
    if (!(useMSAA && [renderer canUseMSAA])) {
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, [renderer textureName], 0);
    }
    
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	
	glViewport(0, 0, currentSize.width, currentSize.height);
	
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
    
    if (isCI) {
        
        glOrtho(0, currentSize.width, 0, currentSize.height, -1, 1);
        glClearColor(0.0, 0.0, 0.0, 0.0);
		glClear(GL_COLOR_BUFFER_BIT);

    }
	
	// CoGeRenderer render
	[renderer renderAtTime:time withArguments:args];
    
    
	// restore states
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	   
    if (useMSAA && [renderer canUseMSAA])
    {
        
        
        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, msaafbo);
        glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbo);
        
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, [renderer textureName], 0);
        
        
        glBlitFramebufferEXT(0, 0, currentSize.width, currentSize.height, 0, 0, currentSize.width, currentSize.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
        
        
        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
        
        
    }
    
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    //
    // its betten the CGLFlushdrawable, because: Submits pending OpenGL commands but does not copy the results to the screen
    //
    glFlushRenderAPPLE();
    
	[self unlock];
}


-(void)dealloc {
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    
    if(fbo)
    {
        glDeleteFramebuffersEXT(1, &fbo);
        fbo = 0;
    }
    
    if(msaafbo)
    {
        glDeleteFramebuffersEXT(1, &msaafbo);
        msaafbo = 0;
    }
    
    if(renderbuffer)
    {
        glDeleteRenderbuffersEXT(1, &renderbuffer);
        renderbuffer = 0;
    }
    
    if(depthbuffer)
    {
        glDeleteRenderbuffersEXT(1, &depthbuffer);
        depthbuffer = 0;
    }
    
    
    if(canvasfbo)
    {
        glDeleteFramebuffersEXT(1, &canvasfbo);
        canvasfbo = 0;
    }
    
    if(canvasdepthbuffer)
    {
        glDeleteRenderbuffersEXT(1, &canvasdepthbuffer);
        canvasdepthbuffer = 0;
    }
    

    [super dealloc];
}

@end
